#region Copyright

// The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in compliance
//  with the License. You may obtain a copy of the License at
//  
//  http://www.mozilla.org/MPL/
//  
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations under 
//  the License.
//  
//  The Initial Developer of the Original Code is Robert Smyth.
//  Portions created by Robert Smyth are Copyright (C) 2008.
//  
//  All Rights Reserved.

#endregion

using System;
using System.Text;
using NoeticTools.StateEngine.Actions;
using NoeticTools.StateEngine.Conditions;
using NoeticTools.StateEngine.Logging;
using NoeticTools.StateEngine.Mapper;
using NoeticTools.StateEngine.Rules;
using NoeticTools.StateEngine.States;


namespace NoeticTools.StateEngine
{
    public class StateEngine<TContext, TState, TStateEvent> : IStateEngine<TContext, TState, TStateEvent>
        where TContext : class
    {
        private readonly ICurrentState<TContext, TState, TStateEvent> currentState;
        private readonly ILogger logger;
        private readonly IStateEngineMapper<TContext, TState, TStateEvent> mapper;

        public StateEngine(IContextualLogManager logManager,
                           IStateEngineMapper<TContext, TState, TStateEvent> mapper,
                           ICurrentState<TContext, TState, TStateEvent> currentState)
        {
            this.mapper = mapper;
            this.currentState = currentState;
            logger = logManager.GetLogger();
        }

        public IStateEngineMapper<TContext, TState, TStateEvent> Mapper
        {
            get { return mapper; }
        }

        public TContext CurrentState
        {
            get { return currentState.State; }
        }

        public string GetDotFormat
        {
            get
            {
                var sb = new StringBuilder();

                sb.AppendLine("digraph finite_state_machine {");
                var currentStateEnum =
                    ((currentState as CurrentState<TContext, TState, TStateEvent>).CurrentStateRules as
                     StateRules<TContext, TState, TStateEvent>).State;
                sb.AppendLine("node [shape = doublecircle] " + currentStateEnum + ";");
                sb.AppendLine("node[shape = circle];");
                sb.AppendLine("rankdir=LR;");

                var allStates = Enum.GetValues(typeof (TState));
                foreach (TState state in allStates)
                {
                    var fromState = state.ToString();
                    var rules =
                        (mapper as StateEngineMapper<TContext, TState, TStateEvent>).StateMap.GetStateRules(state) as
                        StateRules<TContext, TState, TStateEvent>;

                    foreach (var rule in rules.Rules)
                    {
                        if (rule is StateConditionalActionRule<TContext, TStateEvent>)
                        {
                            var action = ((StateConditionalActionRule<TContext, TStateEvent>) rule).Action;
                            var condition = ((StateConditionalActionRule<TContext, TStateEvent>) rule).Condition;
                            if (action is TransitionToStateAction<TContext, TState, TStateEvent> &&
                                condition is OnStateEventCondition<TStateEvent>)
                            {
                                var toState =
                                    ((TransitionToStateAction<TContext, TState, TStateEvent>) action).ToState.ToString();
                                var onEvent = ((OnStateEventCondition<TStateEvent>) condition).StateEvent.ToString();
                                sb.AppendFormat("{0} -> {1} [ label = \"{2}\" ];", fromState, toState, onEvent);
                                sb.AppendLine("");
                            }
                        }
                    }
                }

                sb.AppendLine("}");
                return sb.ToString();
            }
        }

        public TContext OnEvent(TStateEvent stateEvent)
        {
            logger.Debug("On event {0}", stateEvent);
            return currentState.OnEvent(stateEvent);
        }

        public TContext OnEvent<T>(TStateEvent stateEvent, T arg)
        {
            logger.Debug("On event {0}", stateEvent);
            return currentState.OnEvent(stateEvent, arg);
        }
    }
}